#pragma once

#include <iostream>
#include <string>
#include <string.h>
#include <unistd.h>
#include <cstdlib>
// 网络套接字4剑客
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "log.hpp"

// 线程池
#include "Task.hpp"
#include "ThreadPool.hpp"
using namespace std;
namespace server
{

    enum
    {
        Usage_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
        OPEN_ERR
    };

    const static uint16_t gport = 8081;
    const static int listen_num = 10;

    class tcpserver; // 声明存在
    class ThreadDate // 包含tcpserver对象和soc套接字
    {
    public:
        ThreadDate(tcpserver *self, int sock)
            : _self(self), _sock(sock)
        {
        }

    public:
        tcpserver *_self;
        int _sock;
    };

    class tcpserver
    {

    public:
        tcpserver(const uint16_t &port = gport)
            : _listensock(-1), _port(port)
        {
        }
        void initserver()
        {
            // 1、创建套接字
            _listensock = socket(AF_INET, SOCK_STREAM, 0); // AF_INET表示ipv4协议;SOCK_STREAM表示面向字节流(tcp)
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success : %d",_listensock);

            // 2、bind绑定自己的网络信息
            struct sockaddr_in local; // 这个local是栈上面的，我们填了数据，但是os不知道，所以我们要交给os需要用到bind
            memset(&local, 0, sizeof(local));
            // bzero(&local,sizeof(local)); // 将每个字节全部置0
            local.sin_family = AF_INET;
            // local.sin_family = PF_INET; //AF和PF是一样的
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY;
            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

            ////----我们的udp上面两步执行完了就可以进行下面的收发数据操作了，但是tcp还不行

            // 我们tcp是要进行连接的——所以你客户端不能直接和我通信，要先和我建立连接
            // 3、设置socket为监听状态！监听状态让服务端一直监听，获取客户端的新连接
            if (listen(_listensock, listen_num) < 0) // 底层真实的值是listen_num+1
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }
        void start()
        {

            ////4、线程池初始化
            ThreadPool<Task>::getInstance()->run();
            // signal(SIGCHLD,SIG_IGN);
            while (1)
            {
                // 4、获取新连接
                struct sockaddr_in peer; // 存放客户端ip+port
                socklen_t len = sizeof(peer);
                // accept返回值也是一个套接字（文件描述符） —— 是真正tcp服务端和客户端进行通信的套接字
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept socket error , next"); // 我服务端这次连接失败了，但是不影响我下次的连接工作
                    continue;                                        // 服务端继续进行服务！
                }
                logMessage(NORMAL, "accept socket success,get new sock : %d",sock);
                

                // 5、这里就有了套接字sock，未来就是用这个套接字进行通信的。因为tc面向字节流，所以后续全部都是文件操作！

                //// version 版本1 单进程 —— 缺点明显，单进程死循环执行，只有一个客户端才能与服务端通信
                // serverIO(sock);
                // close(sock);

                // // version 版本2 多进程
                // pid_t id = fork();
                // if (id == 0) // 子进程
                // {
                //     // 子进程共享与父进程的所有文件描述符，但是我们子进程只是用来通信交流，所以要关闭不需要的文件描述符
                //     // 防止1、文件描述符泄露；2、对文件描述符的误操作
                //     close(_listensock);
                //     // 我们上面是爷爷进程

                //     // 这里再fork一次，返回值大于0，所以说是父进程，父进程直接exit退出！那么我们下面的waitpid会直接等待成功！
                //     if (fork() > 0)
                //     {
                //         exit(0);
                //     }

                //     // 下面的就是孙子进程！孙子进程进行通信交流，最后退出，但是你孙子进程退出了，管我爷爷进程什么事
                //     // 并且！父进程先退出，孙子进程后退出成为了孤儿进程！！！孤儿进由OS领养，进行管理！
                //     serverIO(sock);
                //     close(sock);
                //     exit(0); // 终止子进程
                // }
                // // 爷爷进程要关闭accept创建的文件描述符，不然我们先创建的文件描述符一直在使用，后面一直创建的文件描述符会越来越大
                // // 就会导致文件描述符泄漏!
                // close(sock);
                // // 父进程
                // // 两个问题：
                // // 1、父进程阻塞等待fork，那不还是一个串行化执行流程吗？那我要多进程干嘛呢？
                // // 2、父进程阻塞等待fork，但是未来服务多起来，父进程不可能等待完所有的子进程，那么有的子进程就没有父进程回收了

                // // 我们就想要阻塞等待的写法
                // pid_t ret = waitpid(id, nullptr, 0);
                // if (ret > 0)
                // {
                //     cout << "waitpid success : " << ret << endl;
                // }
                // // version 版本3 多线程
                // pthread_t tid;
                // // 静态方法无法访问非进程成员，所以需要this
                // // 但是我们的threadRoutine还需要主线程的套接字sock，所以我们定义一个结构体，里面包含tcpserver结构体自己（this）
                // // 和主线程里面的sock套接字
                // ThreadDate *td = new ThreadDate(this, sock);
                // pthread_create(&tid, nullptr, threadRoutine, td);

                // // pthread_join(tid,nullptr);//父线程阻塞等待，和上面一样多此一举，但是多线程没有非阻塞等待。直接在子线程里面线程分离就行

                // version 版本4 线程池
                ThreadPool<Task>::getInstance()->Push(Task(sock, serverIO));
                
            }
        }
        // static void *threadRoutine(void *args) // 静态方法无法访问非进程成员，所以需要this
        // {
        //     pthread_detach(pthread_self()); // 线程分离!!主线程就别join子线程了，你跑你的，我跑我的
        //     ThreadDate *td = static_cast<ThreadDate *>(args);
        //     td->_self->serverIO(td->_sock);
        //     close(td->_sock); // 子线程与客户端通信完成之后，关闭子线程服务端的socket套接字
        //     delete td;
        //     return nullptr;
        // }

        ~tcpserver() {}

    private:
        int _listensock; // 不是用来通信，只是用来监听，获取新连接的
        uint16_t _port;
    };
}